﻿// <copyright file="HistoryEntry.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// This class represents a partial history entry in the CONT section of an App-V SFT file.
    /// </summary>
    /// <remarks>
    /// Because the layout of History Entry changed between versions 4.2 and 4.5 of App-V,
    /// packages created according to 4.5 specifications are not compatible with versions
    /// older than 4.5.
    /// This class represents what the two versions of the history entry have in common.
    /// </remarks>
    public abstract class HistoryEntry : IComparable
    {
        /// <summary>
        /// This field stores the full length of the history entry.
        /// </summary>
        private int size;

        /// <summary>
        /// This field stores the major version number of App-V Sequencer used to create the package.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int sequencerVersionMajor;

        /// <summary>
        /// This field stores the minor version number of App-V Sequencer used to create the package.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int sequencerVersionMinor;

        /// <summary>
        /// This field stores the revision version of App-V Sequencer used to create the package.
        /// </summary>
        /// <remarks>The default value is 0x00000000. This is known as Sequencer Version Patch in
        /// 4.0 - 4.2 packages.</remarks>
        private int sequencerVersionRevision;

        /// <summary>
        /// This field stores the build version of App-V Sequencer used to create the package.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int sequencerVersionBuild;

        /// <summary>
        /// This field stores the length of the account name under which the package was created.
        /// The name is stored immediately following the last history entry. The size does not
        /// include a null terminator.
        /// </summary>
        private int accountNameSize;

        /// <summary>
        /// This field stores the major version number of Microsoft Windows under which the
        /// package was created.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int windowsVersionMajor;

        /// <summary>
        /// This field stores the minor version number of Microsoft Windows under which the
        /// package was created.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int windowsVersionMinor;

        /// <summary>
        /// This field stores the build version of Microsoft Windows under which the package
        /// was created.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private int windowsVersionBuild;
        
        /// <summary>
        /// This field stores the platform ID for the operating system under which the package
        /// was created.
        /// </summary>
        /// <remarks>
        /// This field is currently always initialized to 0x000000002 (VER_PLATFORM_WIN32_NT).
        /// For more detailed description on platform IDs, please refer to the Microsoft Windows
        /// SDK documentation.
        /// </remarks>
        private int platformId;
        
        /// <summary>
        /// This field stores the major version number of the Service Pack for Windows under
        /// which this package was created.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private short servicePackVersionMajor;
        
        /// <summary>
        /// This field stores the minor version number of the Service Pack for Windows under
        /// which this package was created.
        /// </summary>
        /// <remarks>The default value is 0x00000000.</remarks>
        private short servicePackVersionMinor;
        
        /// <summary>
        /// This is a bit field that identifies the product suites for the system under which
        /// the package was created.
        /// </summary>
        private SuiteMaskAttributes suiteMask;
        
        /// <summary>
        /// This field stores additional information about the operating system under which the
        /// package was created.
        /// </summary>
        /// <remarks>The default value should be VER_NT_WORKSTATION.</remarks>
        private SystemType systemType;
        
        /// <summary>
        /// This field is reserved and initialized to all zeroes.
        /// </summary>
        private int processor;
        
        // TODO: Do we need an enum for processor families?

        /// <summary>
        /// This field stores the information about a processor’s family used in the system
        /// under which the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 0x0000024A (PROCESSOR_INTEL_PENTIUM = 586).
        /// For a complete list of processor family IDs, please refer to the Microsoft Windows
        /// SDK documentation and WinNT.h header file.
        /// </remarks>
        private int processorFamily;
        
        /// <summary>
        /// This field stores the memory page size and the granularity of page protection
        /// and commitment used in the system under which the package was created.
        /// </summary>
        /// <remarks>
        /// This field is usually initialized to 0x00001000 (4096). The default value is 0x00001000 (4096).
        /// </remarks>
        private int pageSize;
        
        /// <summary>
        /// This field stores the lowest memory address accessible to applications and
        /// dynamic-link libraries (DLLs) used in the system under which the package was
        /// created.
        /// </summary>
        /// <remarks>The default value is 0x00010000 (65536).</remarks>
        private int minimumAddress;
        
        /// <summary>
        /// This field stores the highest memory address accessible to applications and
        /// DLLs used in the system under which the package was created.
        /// </summary>
        /// <remarks>The default value is 0x7FFEFFFF (2147418111).</remarks>
        private int maximumAddress;
        
        /// <summary>
        /// This field is a bitfield which stores a mask representing the number of processors
        /// configured for the system on which the package was created.
        /// </summary>
        /// <remarks>Bit 0 is processor 0; bit 31 is processor 31.</remarks>
        private int processorMask;
        
        /// <summary>
        /// This field stores the number of a processor in the system under which the package
        /// was created.
        /// </summary>
        /// <remarks>The default value is 1.</remarks>
        private int numberOfProcessors;
        
        /// <summary>
        /// This field stores an architecture-dependent processor level in the system under
        /// which the package was created.
        /// </summary>
        private int processorLevel;
        
        /// <summary>
        /// This field stores an architecture-dependent processor revision in the system
        /// under which the package was created.
        /// </summary>
        /// <remarks>The default value is 0.</remarks>
        private int processorRevision;
        
        /// <summary>
        /// This field stores the year part of the timestamp when this version of the package was saved.
        /// </summary>
        private short modificationTimeStampYear;
        
        /// <summary>
        /// This field stores the month part (in number) of the timestamp when this version of
        /// the package was saved.
        /// </summary>
        private short modificationTimeStampMonth;
        
        /// <summary>
        /// This field stores the weekday when this version of the package was saved.
        /// </summary>
        /// <remarks>The weekday numbering starts from zero, which denotes Sunday, one denotes
        /// Monday, and so forth.</remarks>
        private short modificationTimeStampWeekday;
        
        /// <summary>
        /// This field stores the day part of the timestamp when this version of the package was saved.
        /// </summary>
        private short modificationTimeStampDay;
        
        /// <summary>
        /// This field stores the hour part of the timestamp when this version of the package was saved.
        /// </summary>
        /// <remarks>It is in the UTC (GMT) time zone.</remarks>
        private short modificationTimeStampHour;
        
        /// <summary>
        /// This field stores the minute part of the timestamp when this version of the package was saved.
        /// </summary>
        private short modificationTimeStampMinute;
        
        /// <summary>
        /// This field stores the seconds part of the timestamp when this version of the package was saved.
        /// </summary>
        private short modificationTimeStampSecond;
        
        /// <summary>
        /// This field stores the milliseconds part of the timestamp when this version of the package
        /// was saved.
        /// </summary>
        private short modificationTimeStampMillisecond;
        
        /// <summary>
        /// This field stores the length of the machine hostname under which the package was created.
        /// </summary>
        /// <remarks>The actual hostname is stored in the end of the history entry and has a length
        /// of this many bytes. The default value is 0.</remarks>
        private int hostNameSize;
        
        /// <summary>
        /// This field stores the length of the system directory’s name (typically “c:\windows\system32”)
        /// in the operating system under which the package was created.
        /// </summary>
        /// <remarks>
        /// The actual system directory name is stored in the end of the history entry and has a
        /// length of this many bytes. The default value is 0.
        /// </remarks>
        private int systemDirectoryPathSize;
        
        /// <summary>
        /// This field stores the length of the Windows directory’s name (typically “c:\windows”) in the operating system under which the package was created. The actual Windows directory name is stored in the end of the history entry and has a length of this many bytes. The default value is 0.
        /// </summary>
        private int windowsDirectoryPathSize;
        
        /// <summary>
        /// This field stores the length of the user’s directory name in the operating system under which the package was created. The actual user directory name is stored in the end of the history entry and has a length of this many bytes. The default value is 0.
        /// </summary>
        private int userDirectoryPathSize;
        
        /// <summary>
        /// This field contains the information if the last boot was normal for the machine under which the package was created. The default value is 0 (yes).
        /// </summary>
        private bool lastBoot;
        
        /// <summary>
        /// This field is reserved and initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private int reserved1;
        
        /// <summary>
        /// This field is reserved and initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private int reserved2;
        
        /// <summary>
        /// Indicates whether the current session was remotely controlled in a Terminal Services environment during the package creation. The default value is 0 (no).
        /// </summary>
        private bool remoteDesktop;
        
        /// <summary>
        /// Indicates whether the package was created under a Terminal Services session. The default value is 0 (no).
        /// </summary>
        private bool terminalServices;
        
        /// <summary>
        /// This field is reserved and initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private int reserved3;
        
        /// <summary>
        /// Indicates whether the package was upgraded. This value must match the Save Mode history entry.
        /// </summary>
        private bool upgradeMode;
        
        /// <summary>
        /// This field contains the information about how the package was saved. Normal Save
        /// is the upgrade to an existing package, whereas Save As is a package’s initial
        /// save. This value must match the Upgrade Mode history entry.
        /// </summary>
        private SaveMode saveMode;

        /// <summary>
        /// Initializes a new instance of the HistoryEntry class.
        /// </summary>
        /// <param name="fileReader">
        /// A binary file reader that points to the SFT file.
        /// </param>
        protected HistoryEntry(System.IO.BinaryReader fileReader)
        {
            this.size = fileReader.ReadInt32();
            this.sequencerVersionMajor = fileReader.ReadInt32();
            this.sequencerVersionMinor = fileReader.ReadInt32();
            this.sequencerVersionRevision = fileReader.ReadInt32();
            this.sequencerVersionBuild = fileReader.ReadInt32();
            this.accountNameSize = fileReader.ReadInt32();
            this.windowsVersionMajor = fileReader.ReadInt32();
            this.windowsVersionMinor = fileReader.ReadInt32();
            this.windowsVersionBuild = fileReader.ReadInt32();
            this.platformId = fileReader.ReadInt32();
            this.servicePackVersionMajor = fileReader.ReadInt16();
            this.servicePackVersionMinor = fileReader.ReadInt16();

            short suiteMaskFlag = fileReader.ReadInt16();
            this.suiteMask = (SuiteMaskAttributes)suiteMaskFlag;

            this.systemType = (SystemType)fileReader.ReadInt16();
            this.processor = fileReader.ReadInt32();
            this.processorFamily = fileReader.ReadInt32();
            this.pageSize = fileReader.ReadInt32();
            this.minimumAddress = fileReader.ReadInt32();
            this.maximumAddress = fileReader.ReadInt32();
            this.processorMask = fileReader.ReadInt32();
            this.numberOfProcessors = fileReader.ReadInt32();
            this.processorLevel = fileReader.ReadInt32();
            this.processorRevision = fileReader.ReadInt32();
            this.modificationTimeStampYear = fileReader.ReadInt16();
            this.modificationTimeStampMonth = fileReader.ReadInt16();
            this.modificationTimeStampWeekday = fileReader.ReadInt16();
            this.modificationTimeStampDay = fileReader.ReadInt16();
            this.modificationTimeStampHour = fileReader.ReadInt16();
            this.modificationTimeStampMinute = fileReader.ReadInt16();
            this.modificationTimeStampSecond = fileReader.ReadInt16();
            this.modificationTimeStampMillisecond = fileReader.ReadInt16();
            this.hostNameSize = fileReader.ReadInt32();
            this.systemDirectoryPathSize = fileReader.ReadInt32();
            this.windowsDirectoryPathSize = fileReader.ReadInt32();
            this.userDirectoryPathSize = fileReader.ReadInt32();
            this.lastBoot = fileReader.ReadInt32() == 0;
            this.reserved1 = fileReader.ReadInt32();
            this.reserved2 = fileReader.ReadInt32();
            this.remoteDesktop = fileReader.ReadInt32() == 1;
            this.terminalServices = fileReader.ReadInt32() == 1;
            this.reserved3 = fileReader.ReadInt32();
            this.upgradeMode = fileReader.ReadByte() == 1;

            int saveModeFlag = fileReader.ReadInt32();
            this.saveMode = (SaveMode)saveModeFlag;
        }

        /// <summary>
        /// Gets the version number of the App-V Sequencer used to create the package.
        /// </summary>
        public Version SequencerVersion
        {
            get
            {
                return new Version(this.sequencerVersionMajor, this.sequencerVersionMinor, this.sequencerVersionRevision, this.sequencerVersionBuild);
            }
        }

        /// <summary>
        /// Gets the version number of Microsoft Windows under which the package was created.
        /// </summary>
        public Version WindowsVersion
        {
            get
            {
                return new Version(this.windowsVersionMajor, this.windowsVersionMinor, this.windowsVersionBuild);
            }
        }

        /// <summary>
        /// Gets the version number of the Service Pack for Windows under which the package was created.
        /// </summary>
        public Version ServicePackVersion
        {
            get
            {
                return new Version(this.servicePackVersionMajor, this.servicePackVersionMinor);
            }
        }

        /// <summary>
        /// Gets the date and time at which this version of the package was saved.
        /// </summary>
        /// <remarks>
        /// The DateTime returned is converted to local time.
        /// </remarks>
        public DateTime ModifiedTimeStamp
        {
            get
            {
                return new DateTime(
                    this.modificationTimeStampYear,
                    this.modificationTimeStampMonth,
                    this.modificationTimeStampDay,
                    this.modificationTimeStampHour,
                    this.modificationTimeStampMinute,
                    this.modificationTimeStampSecond,
                    this.modificationTimeStampMillisecond,
                    DateTimeKind.Utc).ToLocalTime();
            }
        }

        /// <summary>
        /// Gets information about how this version of the package was saved.
        /// </summary>
        public SaveMode Save
        {
            get { return this.saveMode; }
        }

        /// <summary>
        /// Gets a value indicating whether the package was created under a Terminal Services session.
        /// </summary>
        public bool TerminalServices
        {
            get { return this.terminalServices; }
        }

        /// <summary>
        /// Gets a value indicating whether the current session was remotely controlled in a Terminal
        /// Services environment during the package creation.
        /// </summary>
        public bool RemoteDesktop
        {
            get { return this.remoteDesktop; }
        }

        /// <summary>
        /// Gets a value indicating whether the last boot was normal for the machine under which the
        /// package was created.
        /// </summary>
        public bool LastBootNormal
        {
            get { return this.lastBoot; }
        }

        /// <summary>
        /// Gets a value that indicates the product suites for the system under which
        /// the package was created.
        /// </summary>
        public SuiteMaskAttributes SequencerProductSuite
        {
            get { return this.suiteMask; }
        }
        
        /// <summary>
        /// Gets the platform ID for the operating system under which the package was created.
        /// </summary>
        /// <remarks>
        /// This field is currently always initialized to 0x000000002 (VER_PLATFORM_WIN32_NT).
        /// For more detailed description on platform IDs, please refer to the Microsoft Windows
        /// SDK documentation.
        /// </remarks>
        public int SequencerPlatformId
        {
            get { return this.platformId; }
        }
        
        /// <summary>
        /// Gets additional information about the operating system under which the
        /// package was created.
        /// </summary>
        /// <remarks>
        /// The default value should be VER_NT_WORKSTATION.
        /// </remarks>
        public SystemType SequencerSystemType
        {
            get { return this.systemType; }
        }
        
        /// <summary>
        /// Gets the information about a processor’s family used in the system
        /// under which the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 0x0000024A (PROCESSOR_INTEL_PENTIUM = 586).
        /// For a complete list of processor family IDs, please refer to the
        /// Microsoft Windows SDK documentation and WinNT.h header file.
        /// </remarks>
        public int SequencerProcessorFamily
        {
            get { return this.processorFamily; }
        }
        
        /// <summary>
        /// Gets the memory page size and the granularity of page protection
        /// and commitment used in the system under which the package was created.
        /// </summary>
        /// <remarks>
        /// This field is usually initialized to 0x00001000 (4,096).
        /// The default value is 0x00001000 (4,096).
        /// </remarks>
        public int SequencerPageSize
        {
            get { return this.pageSize; }
        }
        
        /// <summary>
        /// Gets the lowest memory address accessible to applications and dynamic-link
        /// libraries (DLLs) used in the system under which the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 0x00010000 (65,536).
        /// </remarks>
        public int SequencerMinimumAddress
        {
            get { return this.minimumAddress; }
        }
        
        /// <summary>
        /// Gets the highest memory address accessible to applications and DLLs used in
        /// the system under which the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 0x7FFEFFFF (2,147,418,111).
        /// </remarks>
        public int SequencerMaximumAddress
        {
            get { return this.maximumAddress; }
        }
        
        /// <summary>
        /// Gets a mask representing the number of processors configured for the system on
        /// which the package was created.
        /// </summary>
        /// <remarks>
        /// Bit 0 is processor 0; bit 31 is processor 31.
        /// </remarks>
        public int SequencerProcessorMask
        {
            get { return this.processorMask; }
        }
        
        /// <summary>
        /// Gets the number of processors in the system under which the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 1.
        /// </remarks>
        public int SequencerNumberOfProcessors
        {
            get { return this.numberOfProcessors; }
        }
        
        /// <summary>
        /// Gets an architecture-dependent processor level in the system under which
        /// the package was created.
        /// </summary>
        public int SequencerProcessorLevel
        {
            get { return this.processorLevel; }
        }
        
        /// <summary>
        /// Gets an architecture-dependent processor revision in the system under which
        /// the package was created.
        /// </summary>
        /// <remarks>
        /// The default value is 0.
        /// </remarks>
        public int SequencerProcessorRevision
        {
            get { return this.processorRevision; }
        }
        
        /// <summary>
        /// Gets a value indicating whether the package was upgraded.
        /// </summary>
        /// <remarks>
        /// This value must match the Save Mode history entry.
        /// </remarks>
        public bool PackageWasUpgraded
        {
            get { return this.upgradeMode; }
        }

        /// <summary>
        /// Gets the length of the account name under which the package was created.
        /// </summary>
        protected int SequencerAccountNameSize
        {
            get { return this.accountNameSize; }
        }

        /// <summary>
        /// Gets the length of the machine hostname under which the package was created.
        /// </summary>
        protected int SequencerHostNameSize
        {
            get { return this.hostNameSize; }
        }

        /// <summary>
        /// Gets the length of the system directory's path in the operating system under which the package was created.
        /// </summary>
        protected int SequencerSystemDirectoryPathSize
        {
            get { return this.systemDirectoryPathSize; }
        }

        /// <summary>
        /// Gets the length of the Windows directory's path in the operating system under which the package was created.
        /// </summary>
        protected int SequencerWindowsDirectoryPathSize
        {
            get { return this.windowsDirectoryPathSize; }
        }

        /// <summary>
        /// Gets the length of the user's directory's path in the operating system under which the package was created.
        /// </summary>
        protected int SequencerUserDirectoryPathSize
        {
            get { return this.userDirectoryPathSize; }
        }

        // TODO: Can we get rid of the GetHistoryEntryVersion function and just use the sequencer version?

        /// <summary>
        /// Gets the version number of the history entry.
        /// </summary>
        /// <param name="fileReader">
        /// A binary file reader that points to the SFT file.
        /// </param>
        /// <returns>
        /// Returns the version number for the sequencer used to generate this version of the package.
        /// </returns>
        public static Version GetHistoryEntryVersion(System.IO.BinaryReader fileReader)
        {
            byte[] fileBytes = fileReader.ReadBytes(20);
            fileReader.BaseStream.Seek(-20, System.IO.SeekOrigin.Current);
            int major = BitConverter.ToInt32(fileBytes, 4);
            int minor = BitConverter.ToInt32(fileBytes, 8);
            int revision = BitConverter.ToInt32(fileBytes, 12);
            int build = BitConverter.ToInt32(fileBytes, 16);
            Version v = new Version(major, minor, revision, build);
            return v;
        }

        #region IComparable Members

        /// <summary>
        /// Compares the current instance with another object of the same type and returns
        /// an integer that indicates whether the current instance precedes, follows, or
        /// occurs in the same position in the sort order as the other object.
        /// </summary>
        /// <param name="obj">An object to compare with this instance.</param>
        /// <returns>A 32-bit signed integer that indicates the relative order of the
        /// objects being compared. The return value is less than zero if this instance is
        /// less than obj. The return value is greater than zero if this instance is greater
        /// than obj. The return value is zero if the objects are equal.
        /// </returns>
        public int CompareTo(object obj)
        {
            HistoryEntry compareToObject = obj as HistoryEntry;
            if (compareToObject == null)
            {
                throw new ArgumentException();
            }

            return this.ModifiedTimeStamp.CompareTo(compareToObject.ModifiedTimeStamp);
        }

        #endregion
    }
}
